package org.gicentre.apps.hide;

import java.applet.Applet;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.gicentre.apps.hide.TreemapState.AppearanceType;
import org.gicentre.apps.hide.TreemapState.Layout;
import org.gicentre.data.AndFilter;
import org.gicentre.data.Data;
import org.gicentre.data.DataField;
import org.gicentre.data.EqualsFilter;
import org.gicentre.data.FieldType;
import org.gicentre.data.NotFilter;
import org.gicentre.data.summary.SummariseField;
import org.gicentre.data.summary.SummariseNode;
import org.gicentre.data.summary.SummariseNull;
import org.gicentre.handy.HandyRenderer;
import org.gicentre.tests.handy.HandyDraw2;
import org.gicentre.treemappa.TreeMapNode;
import org.gicentre.treemappa.TreeMapProperties;
import org.gicentre.treemappa.TreeMappa;
import org.gicentre.utils.colour.ColourTable;
import org.gicentre.utils.move.Ease;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;
import processing.core.PGraphics;

public class ThreadedDisplayer extends Thread{

	boolean flagToDoStructuralRebuild;
	boolean flagToDoNonStructuralRebuild;
	TreemapState treemapState;
	Collection<SummariseField> summariseFields;
	Data data;
	private boolean morphTransitionsOn,localColourMin;
	SummariseNode summaryNode;
	private TreeMapProperties treeMapProperties;
	TreemapSummaryNode treemap;
	List<PGraphics> buffers;
	private PFont font;
	HashMap<Object, ColourTable> colours;
	HashMap<Object, ColourScaling> colourScalings;
	PApplet applet;
	HashMap<Integer,TreemapSummaryNode>oldTreemapNodes=new HashMap<Integer, TreemapSummaryNode>();
	HashMap<Integer,Integer>oldTreemapColours=new HashMap<Integer, Integer>();
	private boolean ready=false;
	int treemapW;
	int treemapH;
//	HandyRenderer handyRenderer;


	public ThreadedDisplayer(boolean flagToDoStructuralRebuild,boolean flagToDoNonStructuralRebuild,TreemapState treemapState,Data data,Collection<SummariseField> summariseFields,int treemapW,int treemapH,PApplet sketch,HashMap<Object, ColourTable> colours,PFont font,SummariseNode summaryNode,HashMap<Object, ColourScaling> colourScalings,TreemapSummaryNode treemap,boolean morphTransitionsOn,boolean localColorMin){
		this.flagToDoStructuralRebuild=flagToDoStructuralRebuild;
		this.flagToDoNonStructuralRebuild=flagToDoNonStructuralRebuild;
		this.treemapState=treemapState;
		this.data=data;
		this.summariseFields=summariseFields;
		this.applet=sketch;
		this.treemapW=treemapW;
		this.treemapH=treemapH;
		this.colours=colours;
		this.font=font;
		this.summaryNode=summaryNode;
		this.treemap=treemap;
		this.colourScalings=colourScalings;
		this.morphTransitionsOn=morphTransitionsOn;
		this.buffers=new ArrayList<PGraphics>();
		this.localColourMin=localColorMin;
		treeMapProperties = new TreeMapProperties();
		treeMapProperties.setParameter("width",treemapW+"");
		treeMapProperties.setParameter("height",treemapH+"");
		treeMapProperties.setParameter("border","1");

//		handyRenderer=new HandyRenderer(applet);
	}

	private void addSpecifiedValues(SummariseNode summariseNode){
		//find out which ones NOT to add these values to, be checking if the variable
		//exists further down the hierarchy
		boolean[] shouldAddSpecifiedValues = new boolean[treemapState.hierarchyFields.length];
		Arrays.fill(shouldAddSpecifiedValues,true);
		for (int i=1;i<treemapState.hierarchyFields.length;i++){
			for (int j=0;j<i;j++){
				if (treemapState.hierarchyFields[j].equals(treemapState.hierarchyFields[i])){
					shouldAddSpecifiedValues[i]=false;
				}
			}
		}

		Iterator<SummariseNode> it = summariseNode.iterator();
		while (it.hasNext()){
			SummariseNode node = it.next();
			if (node.getLevel()+1<treemapState.hierarchyFields.length //if it has children
					&& shouldAddSpecifiedValues[node.getLevel()+1] //if this flag has been set (above)
							&& treemapState.filterValues[node.getLevel()+1]==null){ //if no filters
				DataField dataFieldOfChildren = treemapState.hierarchyFields[node.getLevel()+1];
				if (dataFieldOfChildren.getUseAllValues()){
					for(SummariseNode emptySummaryNode : node.getEmptySummariesForChildren(dataFieldOfChildren)){
						node.add(emptySummaryNode);
					}
				}
			}
		}
	}


	public void run() {


		boolean transitionMorph=false;

		if (flagToDoStructuralRebuild){

			AndFilter filter = new AndFilter();
			for (int i=0;i<treemapState.hierarchyFields.length;i++){
				if (treemapState.filterValues[i]!=null){
					filter.add(new EqualsFilter(treemapState.hierarchyFields[i],treemapState.filterValues[i]));
				}
				if (treemapState.appearanceValues[i].get(AppearanceType.INCLUDE_NULLS)==0){
					filter.add(new NotFilter(new EqualsFilter(treemapState.hierarchyFields[i],null)));					
				}
			}
			summaryNode = data.getSummary(treemapState.hierarchyFields,summariseFields,filter);
			if (summaryNode!=null)
				addSpecifiedValues(summaryNode);
		}

//		float lerp;
		if (flagToDoNonStructuralRebuild){
			//start off a transition
//			lerp=0;
			//capture old image for the next time
			//				oldBuffer.copy(buffer,this.bounds.x,this.bounds.y,this.bounds.width,this.bounds.height,0,0,this.bounds.width,this.bounds.height);
		}

		//store old treemap nodes and colours
		if (treemap!=null){
			Iterator<TreeMapNode> it = treemap.iterator();
			while (it.hasNext()){
				TreemapSummaryNode node = (TreemapSummaryNode)it.next();
				oldTreemapNodes.put(node.getPathId().hashCode(),node);
				oldTreemapColours.put(node.getPathId().hashCode(),node.actualColour);
			}
		}
		
		//Build treemap
		if (flagToDoNonStructuralRebuild && summaryNode!=null){
			for (int i=0;i<treemapState.getHierarchyFields().length;i++){
				treeMapProperties.setParameter("border"+i, treemapState.getAppearanceValue(AppearanceType.PADDING,i)+"");
			}
			buildTreemap();
		}

		if (!treemapState.hierHasChanged && morphTransitionsOn
//				&& !oldTreemapNodes.isEmpty()
				){
			//make the transition a morp unless a structural change..
			transitionMorph=true;
		}
		else{
			transitionMorph=false;
		}

		
		if (this.summaryNode==null){
			this.treemap=null;
		}

		//do the final one first
		{
			PGraphics pGraphics=applet.createGraphics(treemapW,treemapH, PApplet.JAVA2D);			
			pGraphics.beginDraw();
			pGraphics.smooth();
			pGraphics.background(255);
			drawTreemapShapes(pGraphics,1,transitionMorph);
			drawTreemapLabels(pGraphics,1,transitionMorph);
			pGraphics.endDraw();

			buffers.add(pGraphics);
		}

		//pregenerate morph frames
		if (transitionMorph){
			float lerp=0;
			if (transitionMorph){
				lerp+=TreemapPanel.LERP_STEP_MORPH;
			}
			else{
				lerp+=TreemapPanel.LERP_STEP_FADE;			
			}

			boolean stop=false;
			while (lerp<1 && !stop){
				PGraphics pGraphics=applet.createGraphics(treemapW,treemapH, PApplet.JAVA2D);
				pGraphics.beginDraw();
				pGraphics.smooth();
				pGraphics.background(255);
				drawTreemapShapes(pGraphics,Ease.cubicBoth(1-lerp),transitionMorph);
				drawTreemapLabels(pGraphics,Ease.cubicBoth(1-lerp),transitionMorph);
				pGraphics.endDraw();
				buffers.add(0,pGraphics);

				if (transitionMorph){
					lerp+=TreemapPanel.LERP_STEP_MORPH;
				}
				else{
					lerp+=TreemapPanel.LERP_STEP_FADE;			
				}
				if (Thread.currentThread().isInterrupted())
					stop=true;
			}
		}
			
		ready=true;
	}


	/**Builds the treemap geometry
	 * 
	 */
	private void buildTreemap(){

		//			//delete all summary nodes whose conditioning values are null if set that way
		//			//also any nodes that should be filtered out
		//			Iterator<SummariseNode> it = summaryNode.iterator();
		//			while (it.hasNext()){
		//				SummariseNode node=it.next();
		//				//nodata nodes
		//				if (node.getParent()!=null && node.getConditioningValue()==null && treemapState.getAppearanceValue(AppearanceType.INCLUDE_NULLS,node.getLevel())==0){
		//					((SummariseNode)node.getParent()).remove(node);
		//				}
		//				//filtered nodes
		//				if (node.getParent()!=null
		//						&& treemapState.getFilterValues()[node.getLevel()]!=null
		//						&& !node.getConditioningValue().equals(treemapState.getFilterValues()[node.getLevel()])){
		//					((SummariseNode)node.getParent()).remove(node);
		//				}
		//			}



		treemap=computeTreemap(summaryNode, treemapState,0,0,treeMapProperties);
	}

	private void drawTreemapLabels(PGraphics pGraphics,float lerp, boolean transitionMorph){
		if (treemap!=null){
			Iterator<TreeMapNode>it = treemap.iterator();
			pGraphics.textFont(font);
			pGraphics.textAlign(PApplet.CENTER,PApplet.CENTER);
			while (it.hasNext()){
				TreemapSummaryNode node = (TreemapSummaryNode)it.next();

				//Don't draw if this or any of its parents is a dummy node (starts with #)
				boolean drawThisNode=true;
				TreemapSummaryNode node2=node;
				while (node2.getParent()!=null && drawThisNode){
					if (node2.getLabel()!=null && node2.getLabel().startsWith("#")){
						drawThisNode=false;
					}
					node2=(TreemapSummaryNode)node2.getParent();
				}

				if (drawThisNode){
					if (treemapState.getAppearanceValue(AppearanceType.SHOW_LABELS,node.getLevel()-1)==1){
						Rectangle2D r=null;
						//need to work out the shape again so we can place the text in the centre of it
						if (lerp<1 && transitionMorph){
							Rectangle2D r2=node.getRectangle();
							Rectangle2D r1=null;
							if (oldTreemapNodes.containsKey(node.getPathId().hashCode())){
								r1=oldTreemapNodes.get(node.getPathId().hashCode()).getRectangle();
							}
							if (r1==null && r2!=null){
								r1=new Rectangle2D.Float((float)r2.getCenterX(),(float)r2.getCenterY(),0,0);
							}
							else if (r2==null && r1!=null){
								r2=new Rectangle2D.Float((float)r1.getCenterX(),(float)r1.getCenterY(),0,0);
							}
							if (r1!=null && r2!=null){
								r = new Rectangle2D.Float(PApplet.lerp((float)r1.getX(),(float)r2.getX(),lerp),PApplet.lerp((float)r1.getY(),(float)r2.getY(),lerp),PApplet.lerp((float)r1.getWidth(),(float)r2.getWidth(),lerp),PApplet.lerp((float)r1.getHeight(),(float)r2.getHeight(),lerp));
							}
						}
						else{
							r=node.getRectangle();
						}
						String label=node.getLabel();
						if (label==null){
							label="<no value>";
						}
						else{
							label=label.replaceAll("\\_", " ");
						}

						//draw the text
						if (r!=null && r.getWidth()>10 && r.getHeight()>10){
							int textSize;
							textSize=(int)treemapState.getAppearanceValue(AppearanceType.LABEL_SIZE,node.getLevel()-1);

							pGraphics.textSize(textSize);
							pGraphics.textLeading(textSize);
							pGraphics.fill(100);
							pGraphics.textAlign(PApplet.CENTER,PApplet.CENTER);

							//adjust the opacity so that labels are lighter towards the 'root'
							int opacity = (int)treemapState.getAppearanceValue(AppearanceType.LABEL_OPACITY,node.getLevel()-1);
							pGraphics.fill(80,opacity);

							pGraphics.pushMatrix();
							pGraphics.translate((float)r.getX(), (float)r.getY());
							//if it doesn't fit, rotate but only if it fits better
							boolean rotated=false;
							int textWidth=(int)pGraphics.textWidth(label);
							if (treemapState.getAppearanceValue(AppearanceType.ALLOW_VERTICAL_LABELS,node.getLevel()-1)==1){
//								if (r.getWidth()<textWidth && r.getHeight()-textWidth>r.getWidth()-textWidth){
									pGraphics.translate((float)(r.getWidth()/2),(float)(r.getHeight()/2));
									pGraphics.rotate(-PApplet.HALF_PI);
									pGraphics.translate(-(float)(r.getHeight()/2),-(float)(r.getWidth()/2));
									rotated=true;
//								}
							}
							//if autosize text, scale appropriately
//							if (treemapState.getAppearanceValue(AppearanceType.AUTOSIZE_LABELS,node.getLevel()-1)==1){
//								if (rotated){
//									if (treemapState.getAppearanceValue(AppearanceType.AUTOSIZE_LABELS,node.getLevel()-1)==1){
//										//										if (textWidth>r.getHeight()||applet.textAscent()+applet.textDescent()>r.getWidth()){
//										pGraphics.scale(PApplet.min((float)r.getHeight()/(textWidth+8),(float)r.getWidth()/(pGraphics.textAscent()+pGraphics.textDescent()+8)));
//										//										}
//									}
//								}
//								else{
//									//									if (textWidth>r.getWidth()||applet.textAscent()+applet.textDescent()>r.getHeight()){
//									pGraphics.scale(PApplet.min((float)r.getWidth()/(textWidth+8),(float)r.getHeight()/(pGraphics.textAscent()+pGraphics.textDescent()+8)));
//									//									}
//								}
//							}
							//draw label
							if (rotated)
								pGraphics.text(label,0,0,(int)r.getHeight(),(int)r.getWidth());
							else
								pGraphics.text(label,0,0,(int)r.getWidth(),(int)r.getHeight());
							pGraphics.popMatrix();
						}
					}
				}
			}
		}
	}

	private void drawTreemapShapes(PGraphics pGraphics,float lerp,boolean transitionMorph){
		//paint background white
		pGraphics.background(255);
//		handyRenderer.setGraphics(pGraphics);
		if (treemap!=null){
			
			Float[] mins=new Float[treemapState.hierarchyFields.length];
			Float[] maxs=new Float[treemapState.hierarchyFields.length];
			Arrays.fill(mins, Float.MAX_VALUE);
			Arrays.fill(maxs, -Float.MAX_VALUE);

			//Work out which levels to colour at
			//Level closest to the end that colour=null
			//CURRENTLY ONLY WORKS WITH THE FIRST COLOUR FIELD SET
			SummariseField[] colourFields=treemapState.getColourFields()[0];
			int colourLevel=-1;
			//find the colourField closest to the leaves
			if (colourFields.length>0){
				for (int i=treemapState.getColourFields()[0].length-1;i>=0;i--){
					if (colourFields[i]!=null){
						colourLevel=i;
						break;
					}
				}

				//work out min/max
				Iterator<TreeMapNode> it = treemap.iterator();
				while (it.hasNext()){
					TreemapSummaryNode node = (TreemapSummaryNode)it.next();
					int level=node.getLevel();
					if (node.getChildren().isEmpty() //if a leaf node
							|| level==colourLevel+1 //OR the node to colour
							||(level-1+1<treemapState.layouts.length //OR one level before abs position 
									&& treemapState.layouts[level-1+1].equals(Layout.ABS_POSITION))){
						Float value=null;
						if (colourFields[level-1] instanceof SummariseNull && node.getSummariseNode().getConditioningField().getFieldType()!=FieldType.STRING){
							value=node.getSummariseNode().getConditioningValueAsFloat();
						}
						else{
							value = node.getSummariseNode().getSummaryAsFloat(colourFields[level-1]);
						}
						if (value!=null && !Float.isInfinite(value) && !Float.isNaN(value)){
							if (mins[level-1]>value){
								mins[level-1]=value;
							}
							if (maxs[level-1]<value){
								maxs[level-1]=value;
							}
						}
					}
				}
				//make min/maxes symmetrical for diverging colour schemes
				//or make min 0 if not localColourMin
				for (int i=0;i<mins.length;i++){
					if (colours.get(colourFields[i])!=null && colours.get(colourFields[i]).getMinIndex()==-1){
						//then it's a diverging scheme
						float max=Math.max(Math.abs(mins[i]),Math.abs(maxs[i]));
						mins[i]=max*-1;
						maxs[i]=max;
					}
					else if (!localColourMin){
						mins[i]=0f;
					}
				}

				//if min equals max, then add one and subtract one
				for (int i=0;i<mins.length;i++){
					if (mins[i]==maxs[i]){
						mins[i]-=1;
						maxs[i]+=1;
					}
				}
				
			}

			//draw them

			pGraphics.stroke(32,32+32);
			pGraphics.strokeWeight(1.5f);

			Iterator<TreeMapNode> it = treemap.iterator();

			while (it.hasNext()){
				TreemapSummaryNode node = (TreemapSummaryNode)it.next();

				//Don't draw if this or any of its parents is a dummy node (starts with #)
				boolean drawThisNode=true;
				TreemapSummaryNode node2=node;
				while (node2.getParent()!=null && drawThisNode){
					if (node2.getLabel()!=null && node2.getLabel().startsWith("#")){
						drawThisNode=false;
					}
					node2=(TreemapSummaryNode)node2.getParent();
				}

				if (drawThisNode){
					int level=node.getLevel();
					int borderwidth=(int)treemapState.getAppearanceValue(AppearanceType.BORDER_WIDTH,level-1);
					if (borderwidth==0){
						pGraphics.noStroke();
					}
					else{
						pGraphics.strokeWeight(borderwidth/2f);
						pGraphics.stroke(150);
					}
					if (node.getChildren().isEmpty() //if a leaf node
							|| level==colourLevel+1 //OR the node to colour
							||(level-1+1<treemapState.layouts.length //OR one level before abs position 
									&& treemapState.layouts[level-1+1].equals(Layout.ABS_POSITION))){
						//work out the colour
						Integer colour = null;
						if (colourFields[level-1]!=null){
							Float value=node.getSummariseNode().getSummaryAsFloat(colourFields[level-1]);
							if (colourFields[level-1] instanceof SummariseNull){//this means use hierarchy colour, if available

								DataField dataField = treemapState.getHierarchyFields()[level-1];
								if (colours.containsKey(dataField)){
									Object v=node.getSummariseNode().getConditioningValue();
									if (dataField.getOrderValues()!=null){//use discrete colours
										value=(float)dataField.getOrderValues().indexOf(v);
										colour=colours.get(treemapState.getHierarchyFields()[level-1]).findColour(value);
									}
									else if (v!=null && dataField.getFieldType()!=FieldType.STRING){//if numeric...
										float fieldValue=((Number)v).floatValue(); 
										colour=colours.get(treemapState.getHierarchyFields()[level-1]).findColour(PApplet.map(fieldValue, mins[level-1], maxs[level-1], 0, 1));
									}
								}
							}
							else if (value!=null){
								float transformedValue=((Number)value).floatValue();
								float transformedMin=mins[level-1];
								float transformedMax=maxs[level-1];
								if (colourScalings.get(colourFields[level-1]).equals(ColourScaling.LOG)){
									boolean isNegative=false;
									if (mins[level-1]!=0){
										if (transformedMin<0)
											transformedMin=-(float)Math.log(Math.abs(mins[level-1]));
										else
											transformedMin=(float)Math.log(Math.abs(mins[level-1]));
									}
									if (maxs[level-1]!=0){
										transformedMax=(float)Math.log(maxs[level-1]);
									}
									if (value<0){
										isNegative=true;
									}
									if (value!=0){
										transformedValue=(float)Math.log(Math.abs(value));
										if (isNegative){
											transformedValue=-transformedValue;
										}
									}
								}
								ColourTable ct = colours.get(colourFields[level-1]); 
								colour=ct.findColour(PApplet.map(transformedValue,transformedMin,transformedMax, ct.getMinIndex(), ct.getMaxIndex()));
							}
							else{
								//noData
								colour=pGraphics.color(200);
							}
						}
						if (colour==null){
							colour=pGraphics.color(255,0);
						}
						node.actualColour=colour;
						if (lerp<1 && colour!=null){
							Integer oldColour=oldTreemapColours.get(node.getPathId().hashCode());
							if (oldColour!=null){
								colour=pGraphics.lerpColor(oldColour.intValue(),colour.intValue(),lerp);
							}
						}
						
						if (colour!=null){
							pGraphics.fill(colour,255);
						}
						else{
							pGraphics.noFill();
						}
						//work out the shape
						Rectangle2D r=null;
						//if morphing, interpolate between previous and current shape
						if (lerp<1 && transitionMorph){
							Rectangle2D r2=node.getRectangle();
							Rectangle2D r1=null;
							if (oldTreemapNodes.containsKey(node.getPathId().hashCode())){
								r1=oldTreemapNodes.get(node.getPathId().hashCode()).getRectangle();
							}
							if (r1==null && r2!=null){
								r1=new Rectangle2D.Float((float)r2.getCenterX(),(float)r2.getCenterY(),0,0);
							}
							else if (r2==null && r1!=null){
								r2=new Rectangle2D.Float((float)r1.getCenterX(),(float)r1.getCenterY(),0,0);
							}
							if (r1!=null && r2!=null){
								r = new Rectangle2D.Float(PApplet.lerp((float)r1.getX(),(float)r2.getX(),lerp),PApplet.lerp((float)r1.getY(),(float)r2.getY(),lerp),PApplet.lerp((float)r1.getWidth(),(float)r2.getWidth(),lerp),PApplet.lerp((float)r1.getHeight(),(float)r2.getHeight(),lerp));
							}
						}
						else{
							//otherwise use the current shape
							r=node.getRectangle();
						}
						//draw the shape
						if (r!=null && r.getBounds().getWidth()>1 && r.getBounds().getHeight()>1){
//							handyRenderer.rect((float)r.getX(),(float)r.getY(),(float)r.getWidth(),(float)r.getHeight());
							pGraphics.rect((float)r.getX(),(float)r.getY(),(float)r.getWidth()+1,(float)r.getHeight()+1);
						}
					}
				}
			}
		}
	}
	
	public boolean isReady(){
		return ready;
	}
	
	static public TreemapSummaryNode computeTreemap(SummariseNode summaryNode, TreemapState treemapState,int offX,int offY,TreeMapProperties treeMapProperties){
		TreeMappa treeMappa=new TreeMappa(treeMapProperties);
		treeMappa.setAllowLeafBorders(true);
		
		Layout[] layouts = treemapState.getLayouts();
		SummariseField[][] orderVars=treemapState.getOrderFields();
		SummariseField[] sizeVars=treemapState.getSizeFields()[0];//just use the first of the sizes - treemaps don't support others

		int startLevel=summaryNode.getLevel()+1;
		HashMap<SummariseNode, TreeMapNode> summaryNode2treemapNode = new HashMap<SummariseNode, TreeMapNode>();

		Iterator<SummariseNode> it =summaryNode.iterator();
		it.next();//ignore this (current) node.
		summaryNode2treemapNode.put(summaryNode, new TreemapSummaryNode("",0,null,null,summaryNode));
		while(it.hasNext()){
			SummariseNode node = it.next();
			String label="";
			double order=0;
			double orderX=0;
			double orderY=0;
			Float size=1f;
//			if (!node.isLeaf()){
				int relativeLevel = node.getLevel()-startLevel;
				label=node.getConditioningValueAsString();
				order = node.getNaturalOrder();
				orderX = node.getNaturalOrder();
				orderY = node.getNaturalOrder();
				if (!layouts[relativeLevel].equals(Layout.TWO_DIMENSIONAL)&&!layouts[relativeLevel].equals(Layout.ABS_POSITION)){
					try{
						order=node.getSummaryAsDouble(orderVars[0][relativeLevel]);
					}
					catch (Exception e){
						//do nothing - uses the natural order (above)
					}
					if (layouts[relativeLevel].equals(Layout.ONE_DIM_STRIP)){
						treeMapProperties.setParameter("layout"+(relativeLevel+startLevel), "strip");
						treeMapProperties.setParameter("align"+(relativeLevel+startLevel), "free");
					}
					else if (layouts[relativeLevel].equals(Layout.ONE_DIM_ORDERED_SQUARIFIED)){
						treeMapProperties.setParameter("layout"+(relativeLevel+startLevel), "orderedSquarified");
						treeMapProperties.setParameter("align"+(relativeLevel+startLevel), "free");
					}
					else if (layouts[relativeLevel].equals(Layout.ONE_DIM_LEFT_RIGHT)){
						treeMapProperties.setParameter("layout"+(relativeLevel+startLevel), "sliceAndDice");
						treeMapProperties.setParameter("align"+(relativeLevel+startLevel), "horizontal");
					}
					else if (layouts[relativeLevel].equals(Layout.ONE_DIM_TOP_BOTTOM)){
						treeMapProperties.setParameter("layout"+(relativeLevel+startLevel), "sliceAndDice");
						treeMapProperties.setParameter("align"+(relativeLevel+startLevel), "vertical");
					}
				}
				else{
					try{
						orderX=node.getSummaryAsDouble(orderVars[0][relativeLevel]);
					}
					catch (Exception e){
						//do nothing - uses the natural order (above)
					}
					try{
						orderY=node.getSummaryAsDouble(orderVars[1][relativeLevel]);
					}
					catch (Exception e){
						//do nothing - uses the natural order (above)
					}
					treeMapProperties.setParameter("layout"+(relativeLevel+startLevel), "spatial");
					treeMapProperties.setParameter("align"+(relativeLevel+startLevel), "free");
				}
				size=1f;
				if (sizeVars[relativeLevel]!=null){
					size= node.getSummaryAsFloat(sizeVars[relativeLevel]);
				}
				if (size==null || Float.isNaN(size) || Float.isInfinite(size) || size==0){
					size=0.0000001f;//if zero, Treemappa ignores it.
				}
//			}
			TreeMapNode treeMapNode=new TreemapSummaryNode(label,order,size,(float)node.getNaturalOrder(),new Point2D.Double(orderX,orderY),node); //put natural order in colour so that if the order values are the same, the natural order is used (this is treemappa behaviour)
			summaryNode2treemapNode.get(node.getParent()).add(treeMapNode);
			summaryNode2treemapNode.put(node,treeMapNode);
		}
		treeMappa.setRoot(summaryNode2treemapNode.get(summaryNode));
		treeMappa.getRoot().sortDescendants();
		
		treeMappa.buildTreeMap();
		//move all the rectangles to their offsets
		Iterator<TreeMapNode> it1=treeMappa.getRoot().iterator(); 
		while(it1.hasNext()){
			Rectangle2D rectangle2D = it1.next().getRectangle();
			if (rectangle2D!=null){
				rectangle2D.setFrame(rectangle2D.getX()+offX,rectangle2D.getY()+offY,rectangle2D.getWidth(),rectangle2D.getHeight());
			}
		}
		
		

		//move any cartographic layouts to their cartographic position
		//First find min/max x/ys
		//Ignore dummy nodes' positions
		//Need to calculate multiple min/max xs/ys for a level if the reference colouring is not root
		//So for each level, we need a hashmap with the path keyed to a Rectangle2D which forms the min/max
		int depth=summaryNode.getDepth();
		HashMap<String,Rectangle2D>[] xyMinMaxes=new HashMap[depth];//coordinate bounding boxes for branches (depending on ORDER_RANGE_REF) 
		HashMap<String,int[]>[] margins=new HashMap[depth];//margins top,right,bottom,left
//		Float[] maxWHs=new Float[depth];//of treemap nodes (in pixels), if nodes are made square
		it1=treeMappa.getRoot().iterator(); 
		while(it1.hasNext()){
			TreemapSummaryNode node=(TreemapSummaryNode)it1.next();
			if (node.getLabel()!=null && !node.getLabel().startsWith("#")){//exclude dummy nodes
				int level=node.getLevel()-1;
				//only calc if layout is abs_position
				if (layouts[level].equals(Layout.ABS_POSITION)){

					if (xyMinMaxes[level]==null){
						xyMinMaxes[level]=new HashMap<String,Rectangle2D>();
						margins[level]=new HashMap<String, int[]>();
					}

					int rangeRefLevel=(int)treemapState.getAppearanceValue(AppearanceType.ORDER_RANGE_REF,level)-1;
					String refPath=getPathUptoLevel(node.getPathId(), rangeRefLevel);
					
					//find width/height for each node, assuming it's square (and half the area and scale by the element size)
					//(correct for element size later)
					float sizeDiff=(float)treemapState.getAppearanceValue(AppearanceType.SIZE_DIFF,level);
//					if (maxWHs[level]==null || wh>maxWHs[level]) maxWHs[level]=wh;

					//find x
					SummariseField orderXVar=orderVars[0][level];
					float x;
					if (orderXVar==null||orderXVar instanceof SummariseNull){
						x=node.getSummariseNode().getNaturalOrder();
					}
					else{
						try{
							x=node.getSummariseNode().getSummaryAsFloat(orderXVar);
						}
						catch (NullPointerException e){
							//nullpointer will be thrown in the no-data case - in this case, move the node offscreen 
							x=-99999;
						}
					}

					//find y
					SummariseField orderYVar=orderVars[1][level];
					float y;
					if (orderYVar==null||orderYVar instanceof SummariseNull){
						y=node.getSummariseNode().getNaturalOrder();
					}
					else{
						try{
							y=node.getSummariseNode().getSummaryAsFloat(orderYVar);
						}
						catch (NullPointerException e){
							//nullpointer will be thrown in the no-data case - in this case, move the node offscreen 
							y=-99999;
						}

					}
					
					if (x!=-99999 && y!=-99999){
						Rectangle2D rangeBox=xyMinMaxes[level].get(refPath);
						int wh=(int)((float)Math.sqrt(((float)node.getRectangle().getWidth()*(float)node.getRectangle().getWidth()/2))+sizeDiff);
						if (rangeBox==null){
							rangeBox=new Rectangle2D.Float(x,y,0,0);
							xyMinMaxes[level].put(refPath,rangeBox);
							margins[level].put(refPath,new int[]{wh/2,wh/2,wh/2,wh/2});
						}
						else{
							int[] marginValues=margins[level].get(refPath);
							if (x-(wh/2)<rangeBox.getMinX()-marginValues[3])
								marginValues[3]=wh/2;
							if (x+(wh/2)>rangeBox.getMaxX()+marginValues[1])
								marginValues[1]=wh/2;
							if (y-(wh/2)<rangeBox.getMinY()-marginValues[0])
								marginValues[0]=wh/2;
							if (y+(wh/2)>rangeBox.getMaxY()+marginValues[2])
								marginValues[2]=wh/2;
							rangeBox.add(x,y);
						}
					}
				}
			}
		}
		
		
		//then move them
		it1=treeMappa.getRoot().iterator(); 
		while(it1.hasNext()){
			TreemapSummaryNode node=(TreemapSummaryNode)it1.next();
			if (node.getLabel()!=null && !node.getLabel().startsWith("#")){
				int level=node.getLevel()-1;

				if (xyMinMaxes[level]!=null){
					if (layouts[level].equals(Layout.ABS_POSITION)){

						Rectangle2D parentR=((TreemapSummaryNode)node.getParent()).getRectangle();
						Rectangle2D r=node.getRectangle();
						float x;
						SummariseField orderXVar=orderVars[0][level];
						if (orderXVar==null||orderXVar instanceof SummariseNull){
							x=node.getSummariseNode().getNaturalOrder();
						}
						else{
							try{
								x=node.getSummariseNode().getSummaryAsFloat(orderXVar);
							}
							catch(NullPointerException e){
								//nullpointer will be thrown in the no-data case - in this case, move the node offscreen 
								x=-99999;
							}
						}
						float y;
						SummariseField orderYVar=orderVars[1][level];
						if (orderYVar==null||orderYVar instanceof SummariseNull){
							y=node.getSummariseNode().getNaturalOrder();
						}
						else{
							try{
								y=node.getSummariseNode().getSummaryAsFloat(orderYVar);
							}
							catch(NullPointerException e){
								//nullpointer will be thrown in the no-data case - in this case, move the node offscreen 
								y=-99999;
							}
						}
						int wH=0;
						int nodeArea=(int)(r.getWidth()*r.getHeight());
						if (nodeArea>0){
							//adjust size so the largest element at this level is sizeDiff number of pixels different.
							//Elements will still be sized proportionally to each other
							float sizeDiff=(float)treemapState.getAppearanceValue(AppearanceType.SIZE_DIFF, level);
							wH=(int)Math.sqrt(nodeArea/2f);
//							wH=(int)PApplet.map(wH,0,maxWHs[level]/2,0,maxWHs[level]/2+sizeDiff);
						}
						int rangeRefLevel=(int)treemapState.getAppearanceValue(AppearanceType.ORDER_RANGE_REF,level)-1;
						String refPath=getPathUptoLevel(node.getPathId(),rangeRefLevel);
						Rectangle2D rangeRect=xyMinMaxes[level].get(refPath);
						int[] marginValues=margins[level].get(refPath);
						float screenX=PApplet.map(x,(float)rangeRect.getMinX(),(float)rangeRect.getMaxX(),(float)parentR.getMinX()+marginValues[3],(float)parentR.getMaxX()-marginValues[1]);
						float screenY=PApplet.map(y,(float)rangeRect.getMinY(),(float)rangeRect.getMaxY(),(float)parentR.getMaxY()-marginValues[0],(float)parentR.getMinY()+marginValues[2]);

						Rectangle2D parentOldR=(Rectangle2D)r.clone();
						r.setFrame(screenX-wH/2,screenY-wH/2,wH,wH);
						Rectangle2D parentNewR=r;
						//then move everything within this node
						for (TreeMapNode node1 : node){
							float newX=PApplet.map((float)node1.getRectangle().getX(),(float)parentOldR.getMinX(),(float)parentOldR.getMaxX(),(float)parentNewR.getMinX(),(float)parentNewR.getMaxX());
							float newY=PApplet.map((float)node1.getRectangle().getY(),(float)parentOldR.getMinY(),(float)parentOldR.getMaxY(),(float)parentNewR.getMinY(),(float)parentNewR.getMaxY());
							float newW=PApplet.map((float)node1.getRectangle().getWidth(),0,(float)parentOldR.getWidth(),0,(float)parentNewR.getWidth());
							float newH=PApplet.map((float)node1.getRectangle().getHeight(),0,(float)parentOldR.getHeight(),0,(float)parentNewR.getHeight());
							node1.getRectangle().setFrame(newX,newY,newW,newH);
						}
					}
				}
			}
		}

		return (TreemapSummaryNode)treeMappa.getRoot();
	}
	
	static String getPathUptoLevel(String path,int level){
		String refPath="/";//path of the subtree that we calculate range for
		int i=0;
		for (String pathComp:path.substring(1).split("/")){
			if (i<level)
				refPath+=pathComp+"/";
			i++;
		}
		return refPath;
	}
}
